package gui;

import java.io.*;
import java.util.*;
import java.math.*;
import java.lang.Math.*;

public class doLZW_compress {

    private int paddedsize;
    private String inputfile;
    private String codewordfile;
    private File file;
    private File com_file;
    private File dict_file;
    private String dictionary_outputfile;
    private Vector key_dictionary;
    private Vector nonpadded_value_dictionary;
    private Vector value_dictionary;
    private String codeword;

    public void doCompression(String args) {
        int filesize = 0;
        inputfile = args;
        try {
            file = new File(inputfile);
            if (!file.exists()) {
                System.out.println("\nFile : \"" + inputfile + "\" does not exist");
                System.exit(0);
            }
            filesize = (int) file.length();
            System.out.println("\nOriginal file : \"" + file.getName() + "\" (" + filesize + " bytes)");
            dictionary_outputfile = inputfile + ".dct";
            codewordfile = inputfile + ".raa";
            nonpadded_value_dictionary = new Vector(3000, 100);
            key_dictionary = new Vector(3000, 100);
            value_dictionary = new Vector(3000, 100);
            InitKeyDictionary(key_dictionary);
            InitValueDictionary(nonpadded_value_dictionary);
            InitValueDictionary(value_dictionary);
            codeword = "";
            doLZW(inputfile, dictionary_outputfile, codewordfile);
            com_file = new File(codewordfile);
            if (!com_file.exists()) {
                System.out.println("\nCompress file : \"" + codewordfile + "\" does not exist");
                System.exit(0);
            }
            int com_filesize = 0;
            com_filesize = (int) com_file.length();
            System.out.println("Compressed file : \"" + com_file.getName() + "\" (" + com_filesize + " bytes)");
            dict_file = new File(dictionary_outputfile);
            
            if (!dict_file.exists()) {
                System.out.println("\nDictionary File  : \"" + dictionary_outputfile + "\" was not created");
                System.exit(0);
            }            
            int dict_filesize = 0;
            dict_filesize = (int) dict_file.length();
            System.out.println("Compression Dictionary file :\n" + " \"" + dictionary_outputfile + "\" (" + dict_filesize + " bytes)");
            System.out.println("Number of entries in Dictionary : " + value_dictionary.size());
            System.out.println("Minimum code size : " + "7");
            String max_code_size = Integer.toBinaryString(value_dictionary.size());
            int int_max_code_size = max_code_size.length();
            System.out.println("Maximum code size : " + int_max_code_size);
            double percentage_compressed = ((double) com_filesize / (double) filesize) * 10000;
            percentage_compressed = Math.ceil(percentage_compressed) / 100;
            System.out.println("The file is compressed to " + percentage_compressed + "% of it's original size.");
        } 
        catch (NullPointerException e) {
       
        }
    }

    public void doLZW(String inputfile, String dictionary_outputfile, String codewordfile) {
        try {
            FileInputStream in = new FileInputStream(inputfile);
            String currentstring;
            int firstchar = in.read();
            Character first_char = new Character((char) firstchar);
            currentstring = first_char.toString();
            String newcurrentstring;
            int nextchar;

            while ((nextchar = in.read()) != -1) {
                newcurrentstring = currentstring + (char) nextchar;
                if (key_dictionary.contains(newcurrentstring)) {
                    currentstring = newcurrentstring;
                } 
                else {
                    int index_of_char = key_dictionary.indexOf(currentstring);
                    codeword = codeword.concat(value_dictionary.elementAt(index_of_char).toString());
                    String value_next_binary = Integer.toBinaryString(value_dictionary.size());
                    key_dictionary.addElement(newcurrentstring);
                    nonpadded_value_dictionary.addElement(value_next_binary);
                    value_dictionary.addElement(value_next_binary);
                    int key_length = value_next_binary.length();
                    if (paddedsize < key_length) {
                        PaddingInDictionary(value_dictionary, key_length);
                    }
                    Character _char = new Character((char) nextchar);
                    currentstring = _char.toString();
                }
            }
            int index_of_char = key_dictionary.indexOf(currentstring);
            codeword = codeword.concat(value_dictionary.elementAt(index_of_char).toString());
            saveDictionary(key_dictionary, nonpadded_value_dictionary, dictionary_outputfile);
            saveCodeword(codeword, codewordfile);
            in.close();
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("The text contained a character that is not in this programs alphabet");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }

    private void PaddingInDictionary(Vector value_dictionary, int value_length) {
        for (int i = 0; i < value_dictionary.size(); i++) {
            String binary = value_dictionary.elementAt(i).toString();
            while (binary.length() < value_length) {
                binary = "0" + binary;
            }
            value_dictionary.setElementAt(binary, i);
        }
        paddedsize = value_length;
    }

    private void InitKeyDictionary(Vector key_dictionary) {
        for (int i = 0; i < 127; i++) {
            Character _char = new Character((char) i);
            String string_character = _char.toString();
            key_dictionary.addElement(string_character);
        }
    }

    private void InitValueDictionary(Vector value_dictionary) {
        int int_binaryvalue = -1;
        String string_binaryvalue = "";

        for (int i = 0; i < 127; i++) {
            int_binaryvalue = int_binaryvalue + 1;
            string_binaryvalue = Integer.toBinaryString(int_binaryvalue);
            value_dictionary.addElement(string_binaryvalue);
        }
        String _binary = Integer.toBinaryString(value_dictionary.size());
        int key_length = _binary.length();
        PaddingInDictionary(value_dictionary, key_length);
    }
    
    private void saveDictionary(Vector key_dictionary, Vector value_dictionary, String dictionary_outputfile) {
        try {
            BufferedWriter dic_out = new BufferedWriter(new FileWriter(dictionary_outputfile));
            for (int i = 0; i < key_dictionary.size(); i++) {
                try {
                    dic_out.write((value_dictionary.elementAt(i).toString()) + "\t\t" + key_dictionary.elementAt(i).toString() + "\n");
                } catch (IOException e) {
                    //
                }
            }
            dic_out.close();
        } catch (IOException e) {
            //
        }
    }

    private void saveCodeword(String codeword, String codewordfile) {
        String _code_word = codeword;
        try {
            FileOutputStream _outputStream = new FileOutputStream(codewordfile);
            BitOutputStream bitout = new BitOutputStream(_outputStream);
            for (int j = 0; j < _code_word.length(); j++) {
                bitout.writeBit(Integer.parseInt(_code_word.substring(j, j + 1)));
            }
            bitout.close();
            _outputStream.close();
        } catch (IOException e) {
            System.out.println("IOException :" + e);
            e.printStackTrace();
        }
    }
}

class BitOutputStream {
 
    private OutputStream out;
    private int buffer;
    private int bitCount;
    
    public BitOutputStream(OutputStream out) {
        this.out = out;
    }
    synchronized public void writeBit(int bit) throws IOException {
        if (out == null) {
            throw new IOException("Already closed");
        }
        if (bit != 0 && bit != 1) {
            throw new IOException(bit + " is not a bit");
        }

        buffer |= bit << bitCount;
        bitCount++;

        if (bitCount == 8) {
            flush();
        }
    }
    private void flush() throws IOException {
        if (bitCount > 0) {
            out.write((byte) buffer);
            bitCount = 0;
            buffer = 0;
        }
    }
    public void close() throws IOException {
        flush();
        out.close();
        out = null;
    }
}
